﻿using System;
using System.IO;
using System.Linq;

using RayDen.Library.Components;
using RayDen.Library.Components.Surface;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Import.OBJLoader;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Frames;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Renderer;
using RayDen.Library.Entity.Scene;
using RayDen.Library.Entity.SceneGraph;
using RayDen.RayEngine.Core;
using RayDen.RayEngine.Core.Devices;
using RayDen.RayEngine.Core.Library;
using RayDen.RayEngine.Core.Threads;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Engines.PathTracer.Volume;
using RayDen.RayEngine.IntersectionEngines;
using RayDen.RayEngine.IntersectionEngines.Cuda;
using RayDen.RayEngine.Managers;
using RayDen.RayEngine.Samplers;
using RayDen.RayEngine.Scene;


namespace RayDen.RayEngine {
    public class MaterialViewRayEngineConfiguration : IRenderConfig {
        public IRendererOutputSettings OutputSettings { get; set; }
        public IRendererQualitySettings QualitySettings { get; set; }
        public bool lowLatency = true;
        public bool AreaLightAsMeshLight = true;

        public RenderingEngines engineType = GlobalConfiguration.Instance.Get("Renderer.Engine", RenderingEngines.PathTracer);
        public bool IsRunning { get; protected set; }

        public static MediumInfo Air = new MediumInfo() { IoR = 1.000277f, Attenuation = 0.1f };
        public static MediumInfo Glass = new MediumInfo() { IoR = 1.46f, Attenuation = 0.05f };

        protected int width, height;
        public SurfaceMaterial ObjectMaterial, PlaneMaterial;

        protected BaseRenderingEngine engine;
        public IntersectionEngine IntersectionDevice;
        public ImageFilmBase Film;
        public RenderThread[] RenderThreads;
        public RayEngineScene Scene;
        public ICamera Camera;
        public SurfaceSampler IntersectionManager;

        private SceneGeometryInfo scene;
        protected MaterialInfo[] mats;

        protected IParameterBag config;

        protected int currentCamera = 0;

        public string FrameName { get;set; }

        public long TotalSamples {
            get { return RenderThreads.Sum(renderThread => renderThread.sampler.TotalSamples); }
        }

        public int Pass {
            get { return RenderThreads.Sum(renderThread => renderThread.sampler.GetPass()) / this.RenderThreadsCount; }

        }

        public MaterialViewRayEngineConfiguration()
        {
           this.config = new ConfigurableEntity(GlobalConfiguration.Instance);
        }


        public MaterialViewRayEngineConfiguration(SurfaceMaterial mat, SurfaceMaterial pm)
        {
            this.config = new ConfigurableEntity(GlobalConfiguration.Instance);
            this.ObjectMaterial = mat;
            this.PlaneMaterial = pm;
        }

        public virtual void Initialize() {
            this.IntersectionDevice =
                new DadeCudaIntersectionDevice(Scene, null);
                //new MCOpenClIntersectionDevice(Scene, lowLatency, 0);

            this.engine = EngineFactory.CreateEngine(engineType, Scene, lowLatency);
            this.Film = (ImageFilmBase)engine.CreateFilm(width, height);
            
            var renderThreadsCount = Math.Min(engine.MaxThreadCount, this.RenderThreadsCount);
            this.RenderThreads = new RenderThread[renderThreadsCount];
            int ht = height / renderThreadsCount;
            var samplingContext = new SamplingContext()
            {
                LightSampler = new LightSampler(Scene),
                SurfaceSampler = new SurfaceSampler(Scene)
            };
            for (int i = 0; i < renderThreadsCount; i++)
            {
                var sampler = engine.CreateSampler(width, height, Scene.MaxPathDepth, i, ht*i, ht);
                samplingContext.PrimarySpaceSampler = sampler;
                RenderThreads[i] = engine.CreateRenderThread(i, IntersectionDevice, Scene, Film, new StillFrameManager(), samplingContext);
            }

            Tracer.TraceLine("Starting {0}", engineType);
        }

        public void OpenFrame(FrameDescription frame)
        {
            this.config.Merge(frame);
            this.OutputSettings = new OutputSettingsInfo(frame);
            this.QualitySettings = new QualitySettingsInfo(frame);

            this.FrameName = frame.FrameName;
            width = frame.Get<int>("ImageWidth");
            height = frame.Get<int>("ImageHeight");
            if (!string.IsNullOrWhiteSpace(frame.WorkingDir))
                Directory.SetCurrentDirectory(frame.WorkingDir);
   
            if (Scene == null) {
                var svc = new SceneGraphService();
                svc.Populate(new GlobalIndexObjLoader(), new MaterialLoader());

                var builder = new SceneBuilder() { SGService = svc };
                builder.Setup();
                this.Scene = builder.OpenFrame(frame, AreaLightAsMeshLight);
                this.Scene.DefaultLightGain = config.Get("LightGain", new RgbSpectrum(1.0f));
                this.Scene.MaxPathDepth = config.Get("MaxPathDepth", 15);
                this.Scene.RussianRuletteImportanceCap = config.Get("RRImportanceCap", 0.75f);
                this.Scene.MaxPaths = config.Get("PathBufferSize", 65536 >> 10);
                this.Scene.ShadowRayCount = config.Get("ShadowRayCount", 16);
                this.Scene.TextureSamplingQuality = this.QualitySettings.TextureSamplingQuality;
                this.Scene.AreaLightAsMesh = this.AreaLightAsMeshLight;
                this.Scene.MatLib = new OptimizedBsdfLibrary();

                this.Scene.MatLib.Populate(builder.MaterialInfos.ToArray());

                (this.Scene.MatLib as OptimizedBsdfLibrary) .Replace("Object_MaterialView_Test", this.ObjectMaterial);
                (this.Scene.MatLib as OptimizedBsdfLibrary).Replace("Plane_Material", this.PlaneMaterial);

                foreach (var brdf in ObjectMaterial.Where(brdf => brdf != null))
                {
                    builder.ProcessMaterial(0, brdf.MaterialData);
                }
                foreach (var brdf in PlaneMaterial.Where(brdf => brdf != null))
                {
                    builder.ProcessMaterial(1, brdf.MaterialData);
                }

                RenderThreadsCount = config.Get("RenderThreadsCount",1);
                this.scene = builder.SceneGeo;

                var sceneBound = new AABB(scene.BoundingSphereCenter - scene.BoundingSphereRadius,
                                          scene.BoundingSphereCenter + scene.BoundingSphereRadius);
                sceneBound.Expand(1f);

                //var viewBound = new AABB(Scene.Camera.Target - 5f, Scene.Camera.Target + 5f);
                Scene.VolumeIntegrator = new SingleScatteringIntegrator(sceneBound, sceneBound.Size.Length / 10f, 0.15f, RgbSpectrum.UnitSpectrum()*0.095f, new RgbSpectrum(0.0005f));
            }
         

            this.Initialize();

        }

        public void NextCamera() {
            if (currentCamera < Scene.Cameras.Count) {
                currentCamera++;
                this.Camera = Scene.Cameras[currentCamera];
                Scene.Camera = (BaseCamera)this.Camera;
            }
        }

        public void PrevCamera() {
            if (currentCamera >0) {
                currentCamera--;
                this.Camera = Scene.Cameras[currentCamera];
                Scene.Camera = (BaseCamera)this.Camera;
            }
        }


        public void RunCommand(int index)
        {
            
        }
        public int RenderThreadsCount { get; set; }
        private DateTime start;
        public void StartRenderer() {
            IsRunning = true;
            start = DateTime.UtcNow;
            if (this.RenderThreadsCount > this.engine.MaxThreadCount)
                this.RenderThreadsCount = this.engine.MaxThreadCount;

            for (int i = 0; i < RenderThreadsCount; i++) {
                this.RenderThreads[i].Start();
                //this.pixelDevices[i].Start();
            }
        }

        public string GetStats() {
            var totalSamples = RenderThreads.Sum(renderThread => renderThread.sampler.TotalSamples);
            var samplesPerSec = totalSamples / (float)(DateTime.UtcNow - start).TotalSeconds;
            
            return string.Format("{0:F5} MSamples/sec ; {1} Elapsed ; {2} Samples Per Pixel",
                //{0:F5} MRays/sec  this.IntersectionDevice.GetPerformance() / 1000000d, 
                samplesPerSec / 1000000d, DateTime.UtcNow - start, Pass
                //_renderThreads[0].sampler.GetPass(), 
                //Engine.GetStats()
                );
        }

        public void Save(string fn) {
            (this.Film).SavePNG(fn, GetStats().Split(';').Select(item=>item.Trim()).Union(new [] { string.Format("Engine - {0}",this.engineType) }).ToArray());
        }

        public void Dispose() {
            if (this.IntersectionDevice != null) {
                this.IntersectionDevice.Dispose();
            }
        }


        public void Interrupt() {
            if (IntersectionDevice.IsRunning) {
                IntersectionDevice.Stop();
            }
            for (int i = 0; i < RenderThreadsCount; i++) {
                this.RenderThreads[i].Stop();
            }
        }

        #region IRenderConfig Members

        public string SavePath {
            get { return config.Get("ImageSaveDirectory", @"F:\Dev\Images\MovieFactory\"); }
        }
        public int Width {
            get { return width; }
        }

        public int Height {
            get { return height; }
        }

        public void SaveImage(string fn) {
            this.Save(fn);
        }

        public void SaveRawImage(string fn) {
            this.Film.SaveRaw(fn);
        }

        public void Start() {
            this.StartRenderer();
        }

        public IImagePlaneComponent GetImagePlane() {
            return (this.Film);
        }


        public virtual void ConsumeJob(IRenderJob job, Action<IRenderJobResult> resultCallback) {
            throw new NotImplementedException();
        }

        #endregion
    }
}
